package uk.ac.cam.cl.projectcharlie2010.game;

import java.util.Enumeration;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import uk.ac.cam.cl.projectcharlie2010.engines.Engine;
import uk.ac.cam.cl.projectcharlie2010.game.player.Player;
import uk.ac.cam.cl.projectcharlie2010.game.player.PlayerEngine;
import uk.ac.cam.cl.projectcharlie2010.game.player.PlayerHuman;

import ch.nostromo.tiffanys.engines.EngineListener;
import ch.nostromo.tiffanys.engines.EngineResult;
import ch.nostromo.tiffanys.game.GameControllerException;
import ch.nostromo.tiffanys.game.GameControllerListener;
import ch.nostromo.tiffanys.game.board.Board;
import ch.nostromo.tiffanys.game.board.BoardException;
import ch.nostromo.tiffanys.game.chess.ChessException;
import ch.nostromo.tiffanys.game.game.Game;
import ch.nostromo.tiffanys.game.game.GameException;
import ch.nostromo.tiffanys.game.game.GameIllegalMoveException;
import ch.nostromo.tiffanys.game.game.GameState;
import ch.nostromo.tiffanys.game.move.Move;
import ch.nostromo.tiffanys.game.move.MoveException;
import ch.nostromo.tiffanys.game.move.MoveInput;
import ch.nostromo.tiffanys.game.move.MoveTools;
import ch.nostromo.tiffanys.game.pgn.PGN;
import ch.nostromo.tiffanys.game.player.PlayerException;

public class GameController implements Cloneable {

	/** Controller listeners (not cloned by clone!) */
	private transient Vector<GameControllerListener> listener;

	/** Current calculating engine (not cloned by clone!) */
	private transient Engine currentCalculatingEngine = null;

	static Logger logger = Logger.getLogger(GameController.class.getName());

	/** Player white (cloned) */
	private Player playerWhite;

	/** Player black (cloned) */
	private Player playerBlack;

	/** Event date (cloned) */
	private String eventDate;

	/** Event name (cloned) */
	private String eventName;

	/** Event site (cloned) */
	private String eventSite;

	/** Event round (cloned) */
	private String eventRound;

	/** Game (cloned) */
	Game game = null;

	@Override
	public GameController clone() throws CloneNotSupportedException {
		GameController result = (GameController) super.clone();

		if (game != null) {
			result.game = (Game) game.clone();
		}

		if (playerBlack != null) {
			result.playerBlack = (Player) playerBlack.clone();
		}

		if (playerWhite != null) {
			result.playerWhite = (Player) playerWhite.clone();
		}

		return result;
	}

	public GameController(Player playerWhite, Player playerBlack, Game game) {
		this.playerWhite = playerWhite;
		this.playerBlack = playerBlack;
		this.game = game;
	}

	public GameController(Player playerWhite, Player playerBlack, Board board,
			int colorToMove) {
		this(playerWhite, playerBlack, new Game(board, colorToMove));
	}

	public GameController(Player playerWhite, Player playerBlack) {
		this(playerWhite, playerBlack, new Board(), Game.COLOR_WHITE);
	}

	public GameController(Player playerWhite, Player playerBlack, String fen)
			throws GameControllerException {
		this(playerWhite, playerBlack, new Game(fen));
	}

	public GameController() {
		this(new PlayerHuman("Undef White"), new PlayerHuman("Undef Black"));
	}

	public void switchSides() {
		Player tmpPlayer = playerWhite;
		playerWhite = playerBlack;
		playerBlack = tmpPlayer;
	}

	public Engine getCurrentEngine() throws GameControllerException {

		Player tmpPlayer;
		if (game.getCurrentColorToMove() == Game.COLOR_WHITE) {
			tmpPlayer = playerWhite;
		} else {
			tmpPlayer = playerBlack;
		}

		if (tmpPlayer instanceof PlayerEngine) {
			try {
				return ((PlayerEngine) tmpPlayer).getEngineInstance();
			} catch (PlayerException e) {
				logger.severe("Unable to create engine instance: "
						+ e.getMessage());
				throw new GameControllerException(
						"Unable to create engine instance: " + e.getMessage());
			}
		} else {
			logger.severe("Human player does not have engine!");
			throw new GameControllerException(
					"Human player does not have engine!");
		}
	}

	public Engine getWaitingEngine() throws GameControllerException {

		Player tmpPlayer;
		if (game.getCurrentColorToMove() != Game.COLOR_WHITE) {
			tmpPlayer = playerWhite;
		} else {
			tmpPlayer = playerBlack;
		}

		if (tmpPlayer instanceof PlayerEngine) {
			try {
				return ((PlayerEngine) tmpPlayer).getEngineInstance();
			} catch (PlayerException e) {
				logger.severe("Unable to create engine instance: "
						+ e.getMessage());
				throw new GameControllerException(
						"Unable to create engine instance: " + e.getMessage());
			}
		} else {
			logger.severe("Human player does not have engine!");
			throw new GameControllerException(
					"Human player does not have engine!");
		}
	}

	public Player getPlayerWhite() {
		return playerWhite;
	}

	public Player getPlayerBlack() {
		return playerBlack;
	}

	public Player getCurrentPlayer() {
		if (game.getCurrentColorToMove() == Game.COLOR_WHITE) {
			return playerWhite;
		} else {
			return playerBlack;
		}
	}

	public void takeBackMove() throws GameException {
		game.takeBackMove();
	}

	public int getCurrentColorToMove() {
		return game.getCurrentColorToMove();
	}

	public Move[] getCurrentLegalMoves() throws GameControllerException {
		return game.getCurrentLegalMoves();
	}

	public boolean isPromotionMove(MoveInput moveInput)
			throws GameControllerException {
		return game.isPromotionMove(moveInput);
	}

	public GameState getCurrentGameState() throws GameControllerException {
		return game.getCurrentGameState();
	}

	public Move getLastMove() {
		return game.getLastMove();
	}

	public GameControllerResult computeNextMoveNow(boolean enterMove)
			throws GameControllerException {
		currentCalculatingEngine = getCurrentEngine();
		currentCalculatingEngine.setEngineSearchParameters(game
				.getCurrentBoard(), game.getCurrentColorToMove(), game
				.getPlayedPGN(0));

		currentCalculatingEngine.startEngine();
		try {
			currentCalculatingEngine.join();
		} catch (InterruptedException e) {
			// ignore
		}

		return mapAndEnterResult(currentCalculatingEngine.getEngineResult(),
				enterMove);
	}

	public void computeNextMove(final boolean enterMove)
			throws GameControllerException {
		currentCalculatingEngine = getCurrentEngine();

		currentCalculatingEngine.setEngineSearchParameters(game
				.getCurrentBoard(), game.getCurrentColorToMove(), game
				.getPlayedPGN(0));
		//FIXED: "fixme" Why does it add an engine listener on each call?!
		//Because a new engine is created each time, for threading reasons
		currentCalculatingEngine.addEngineListener(new EngineListener() {

			public void engineFinished(EngineResult engineResult) {
				processComputeNextMoveFinishedEvent(mapAndEnterResult(
						engineResult, enterMove));
			}

			public void depthFinished(EngineResult engineResult) {
				processComputeNextMoveDepthFinishedEvent(mapAndEnterResult(
						engineResult, false));
			}

		});

		currentCalculatingEngine.startEngine();
	}

	public void engineMoveNow() {
		if (currentCalculatingEngine != null) {
			currentCalculatingEngine.setRunning(false);
		}

	}

	protected GameControllerResult mapAndEnterResult(EngineResult engineResult,
			boolean enterMove) {
		try {
			MoveInput calculatedMove = engineResult.getSelectedMove();
			
			//CHANGED!! Getting the FEN for Harry
			
			String FEN = getFEN();

			// For logging, alter moves temporarly
			Move[] tmpList = MoveTools.alterMoves(engineResult
					.getCalculatedMoves(), game.getCurrentBoard(), game
					.getCurrentColorToMove());
			String moveList = MoveTools.getMovesDump(tmpList);
			Move alteredMove = MoveTools.alterMove(calculatedMove, game
					.getCurrentBoard(), game.getCurrentColorToMove());

			if (enterMove) {
				logger.fine("EngineStats: " + engineResult.getEngineInfo());
				logger.fine("Calculated moves list for next move by engine:"
						+ moveList);
				game.enterNextMove(calculatedMove);
			}
			return new GameControllerResult(alteredMove, tmpList, engineResult, FEN);
		} catch (GameIllegalMoveException e) {
			logger.log(Level.SEVERE, "GameIllegalMoveException", e);
			return null;
		} catch (GameException e) {
			logger.log(Level.SEVERE, "GameException", e);
			return null;
		} catch (BoardException e) {
			logger.log(Level.SEVERE, "BoardException", e);
			return null;
		} catch (ChessException e) {
			logger.log(Level.SEVERE, "ChessException", e);
			return null;
		} catch (MoveException e) {
			logger.log(Level.SEVERE, "MoveException", e);
			return null;
		}
	}

	public void enterNextMove(MoveInput move) throws GameControllerException {
		game.enterNextMove(move);
		logger.fine("PGN: \n" + PGN.exportPGN(this));
		logger.fine("FEN: " + PGN.exportFEN(this.getGame()));
	}

	public void enterNextMove(String move) throws GameControllerException {
		game.enterNextMove(move);
		if (logger.isLoggable(Level.FINE)) {
			logger.fine("PGN: \n" + PGN.exportPGN(this));
			logger.fine("FEN: " + PGN.exportFEN(this.getGame()));
		}
	}

	public Board getCurrentBoard() {
		return game.getCurrentBoard();
	}

	public String getPGN() throws GameControllerException {
		return PGN.exportPGN(this);
	}

	public String getFEN() {
		return getGame().getFEN();
	}

	public synchronized void addGameControllerListener(GameControllerListener m) {
		if (listener == null) {
			listener = new Vector<GameControllerListener>(5, 5);
		}
		if (!listener.contains(m)) {
			listener.addElement(m);
		}
	}

	public synchronized void removeGameControllerListener(
			GameControllerListener m) {
		if (listener.contains(m)) {
			listener.remove(m);
		}
	}

	synchronized void processComputeNextLegalMovesFinishedEvent(
			GameControllerResult gameControllerResult) {
		if (listener != null) {
			for (Enumeration<GameControllerListener> e = listener.elements(); e
					.hasMoreElements();) {
				(e.nextElement())
						.computeNextLegalMovesFinished(gameControllerResult);
			}
		}
	}

	synchronized void processComputeNextMoveFinishedEvent(
			GameControllerResult gameControllerResult) {
		if (listener != null) {
			for (Enumeration<GameControllerListener> e = listener.elements(); e
					.hasMoreElements();) {
				(e.nextElement()).computeNextMoveFinished(gameControllerResult);
			}
		}
	}

	synchronized void processComputeNextMoveDepthFinishedEvent(
			GameControllerResult gameControllerResult) {
		if (listener != null) {
			for (Enumeration<GameControllerListener> e = listener.elements(); e
					.hasMoreElements();) {
				(e.nextElement())
						.computeNextMoveDepthFinished(gameControllerResult);
			}
		}
	}

	public Game getGame() {
		return game;
	}

	public String getEventDate() {
		return eventDate;
	}

	public void setEventDate(String eventDate) {
		this.eventDate = eventDate;
	}

	public String getEventName() {
		return eventName;
	}

	public void setEventName(String eventName) {
		this.eventName = eventName;
	}

	public String getEventSite() {
		return eventSite;
	}

	public void setEventSite(String eventSite) {
		this.eventSite = eventSite;
	}

	public String getEventRound() {
		return eventRound;
	}

	public void setEventRound(String round) {
		this.eventRound = round;
	}

	public void setPlayerBlack(Player playerBlack) {
		this.playerBlack = playerBlack;
	}

	public void setPlayerWhite(Player playerWhite) {
		this.playerWhite = playerWhite;
	}

}
